/*    file: main.cpp
 *    desc:
 * 
 * created: 2016-04-06
 *  author: chuanjiang.zh@qq.com
 * company: 
 */

#include <stdio.h>
#include <iostream>

#include "BasicType.h"

#include "SharedPtr.h"
#include "TFileUtil.h"
#include "BasicMacro.h"
#include "TStringUtil.h"
#include <assert.h>
#include <errno.h>
#include <memory>

#include "DateTime.h"
#include "Path.h"


#include "light_pcapng_ext.h"
#include "NetHeader.h"
#include "NetHeaderUtil.h"
#include "Socket.h"

#include <functional>

#include "RtpPacketWriter.h"
#include "TByteBuffer.h"


#ifdef WIN32
#else
#include <netinet/in.h>
#include <arpa/inet.h>
#endif //




typedef  std::function< bool(const tcp_hdr* tcphdr, const uint8_t* data, int len)>   TcpPacketHandler;


class Application 
{
public:
	Application():
        m_pktCount()
	{
        comn::Socket::startup();

		std::string workdir = comn::Path::getWorkDir();

        m_buffer.ensure(1024 * 2);
	}

	~Application()
	{

		comn::Socket::cleanup();
	}

    bool open(const std::string& codec, const std::string& filepath)
    {
        return m_writer.open(codec, filepath);
    }

    void close()
    {
        m_writer.close();
    }

    int consumeData(const uint8_t*& data, int& len, int wanted)
    {
        int count = std::min(wanted, len);
        if (count > 0)
        {
            m_buffer.write(data, count);
            data += count;
            len -= count;
        }
        return count;
    }

    typedef std::function< void(const uint8_t* data, int len)> DataHandler;

    int parseData(const uint8_t* data, int len, DataHandler handler)
    {
        int count = 0;
        if (m_buffer.size() < 2)
        {
            int wanted = (int)(2 - m_buffer.size());
            count += consumeData(data, len, wanted);

            if (count < wanted)
            {
                return count;
            }
        }
        
        uint16_t prefix = *((uint16_t*)m_buffer.data());
        prefix = ntohs(prefix);

        if (prefix <= 0 || prefix >= 1452)
        {
            m_buffer.clear();
            return count + len;
        }

        //printf("prefix: %d\n", prefix);
        
        int wanted = (prefix + 2) - (int)m_buffer.size();
        int ret = consumeData(data, len, wanted);
        count += ret;

        if (ret >= wanted)
        {
            handler(m_buffer.data()+2, (int)m_buffer.size()-2);
            m_buffer.clear();
        }
        return count;
    }

    bool handleTcpPacket(const tcp_hdr* tcphdr, const uint8_t* data, int len)
    {
        int minSize = std::min(len, 16);
        std::string str = comn::StringCast::toHexString(data, minSize);
        printf("tcp. seq: %u, size: %d. data: %s\n", ntohl(tcphdr->seq_no), len, str.c_str());

        auto handler = [&](const uint8_t* data, int size)
        {
            m_writer.write(data, size);
        };

        do
        {
            int count = parseData(data, len, handler);
            data += count;
            len -= count;
            
        } while (len > 0);

        m_pktCount++;
        return true;
    }

    bool dump(const std::string& filepath, int dstPort)
    {
        auto handler = std::bind(&Application::handleTcpPacket, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
        return process(filepath, dstPort, handler);
    }
	
    bool process(const std::string& filepath, int dstPort, TcpPacketHandler handler)
    {
        light_pcapng_t *pcapng = light_pcapng_open_read(filepath.c_str(), LIGHT_FALSE);
        if (pcapng == NULL)
        {
            return false;
        }

        int pktCount = 0;
        while (true)
        {
            light_packet_header pkt_header;
            const uint8_t *pkt_data = NULL;
            int res = 0;

            res = light_get_next_packet(pcapng, &pkt_header, &pkt_data);
            if (!res)
            {
                break;
            }

            if (pkt_data == NULL)
            {
                continue;
            }

            const uint8_t* data = pkt_data;
            int size = pkt_header.captured_length;

            const ip_hdr* iphdr = NetHeaderUtil::getIpHdr(data, size);
            const tcp_hdr* tcphdr = NetHeaderUtil::getTcpHdr(data, size);
            const udp_hdr* udphdr = NetHeaderUtil::getUdpHdr(data, size);

            if (!iphdr)
            {
                continue;
            }

            if (!tcphdr)
            {
                continue;
            }

            pktCount++;

            int tcpHdrSize = (int)(tcphdr->thl) << 2;

            data += sizeof(eth_hdr) + sizeof(ip_hdr) + tcpHdrSize;
            size -= sizeof(eth_hdr) + sizeof(ip_hdr) + tcpHdrSize;

            if (size <= 0)
            {
                continue;
            }

            if ((tcphdr->flag & 0x002) == 0x002)
            {
                continue;
            }

            NetHeaderUtil::Host host = NetHeaderUtil::matchHostPort(tcphdr, htons(dstPort));
            if (host == NetHeaderUtil::kSrc)
            {
                //
            }
            else if (host == NetHeaderUtil::kDst)
            {
                comn::DateTime dt(pkt_header.timestamp.tv_sec, pkt_header.timestamp.tv_usec / 1000);
                std::string str = dt.toPreciseString();
                printf("pcap pkt count: %d, time: %s\n", pktCount, str.c_str());

                if (!handler(tcphdr, data, size))
                {
                    break;
                }
            }
        }

        light_pcapng_close(pcapng);

        return true;
    }


    RtpPacketWriter m_writer;

    comn::ByteBuffer    m_buffer;
    
    int m_pktCount;

};


int main(int argc, char** argv)
{
    if (argc < 2)
    {
        printf("TcpRtpDump filepath dst_port codec outfile");
        return 0;
    }

    std::string filepath = argv[1];
	uint16_t port = 0;
    comn::StringCast::toValue(argv[2], port);

    std::string codec = "h264";
    std::string outpath = "out.data";

    if (argc > 3)
    {
        codec = argv[3];
    }
    comn::StringUtil::toUpper(codec);

    if (argc > 4)
    {
		outpath = argv[4];
    }

	Application app;

    app.open(codec, outpath);

    app.dump(filepath, port);

    app.close();

	return 0;
}




